/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2025 CCBlueX
 *
 * LiquidBounce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LiquidBounce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
 */

package net.ccbluex.liquidbounce.features.module.modules.render

import net.ccbluex.liquidbounce.event.events.PacketEvent
import net.ccbluex.liquidbounce.event.events.WorldChangeEvent
import net.ccbluex.liquidbounce.event.events.WorldRenderEvent
import net.ccbluex.liquidbounce.event.handler
import net.ccbluex.liquidbounce.features.module.Category
import net.ccbluex.liquidbounce.features.module.ClientModule
import net.ccbluex.liquidbounce.render.drawPlane
import net.ccbluex.liquidbounce.render.engine.type.Color4b
import net.ccbluex.liquidbounce.render.renderEnvironmentForWorld
import net.ccbluex.liquidbounce.render.withPositionRelativeToCamera
import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket
import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket
import net.minecraft.network.packet.s2c.play.ChunkDeltaUpdateS2CPacket
import net.minecraft.network.packet.s2c.play.UnloadChunkS2CPacket
import net.minecraft.util.math.ChunkPos
import net.minecraft.util.math.Vec3d
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.abs
import kotlin.math.max

/**
 * NewChunks module
 *
 * Highlights chunks that are likely newly generated.
 */
object ModuleNewChunks : ClientModule("NewChunks", Category.RENDER) {

    private val renderDistance by int("RenderDistance", 32, 4..128, "chunks")
    private val renderY by float("RenderY", 0.0f, -64.0f..320.0f)
    private val autoY by boolean("AutoY", false)

    private val smooth by boolean("Smooth", true)
    private val persist by boolean("Persist", true)

    private val newColor by color("NewColor", Color4b(0, 255, 0, 80))
    private val oldColor by color("OldColor", Color4b(255, 0, 0, 80))

    private val chunks = ConcurrentHashMap<ChunkPos, Boolean>()

    private fun reset() {
        chunks.clear()
    }

    override fun onDisabled() = reset()

    @Suppress("unused")
    private val worldChangeHandler = handler<WorldChangeEvent> {
        reset()
    }

    @Suppress("unused")
    private val packetHandler = handler<PacketEvent> { event ->
        when (val packet = event.packet) {
            is UnloadChunkS2CPacket -> {
                if (!persist) {
                    chunks.remove(packet.pos)
                }
            }

            is ChunkDataS2CPacket -> {
                val pos = ChunkPos(packet.chunkX, packet.chunkZ)

                chunks.putIfAbsent(pos, false)
            }

            is ChunkDeltaUpdateS2CPacket -> {
                packet.visitUpdates { bp, state ->
                    val fluid = state.fluidState
                    if (!fluid.isEmpty && !fluid.isStill) {
                        chunks[ChunkPos(bp)] = true
                    }
                }
            }

            is BlockUpdateS2CPacket -> {
                val fluid = packet.state.fluidState
                if (!fluid.isEmpty && !fluid.isStill) {
                    chunks[ChunkPos(packet.pos)] = true
                }
            }
        }
    }

    @Suppress("unused")
    private val renderHandler = handler<WorldRenderEvent> { event ->
        if (chunks.isEmpty()) return@handler

        val maxDist = renderDistance.toDouble() * 16.0
        val renderDistSq = maxDist * maxDist

        val drawY = if (autoY) player.y - 100.0 else renderY.toDouble()

        renderEnvironmentForWorld(event.matrixStack) {
            startBatch()
            for ((chunk, isNew) in chunks) {
                val chunkX = chunk.startX
                val chunkZ = chunk.startZ

                if (player.squaredDistanceTo(chunkX + 8.0, player.y, chunkZ + 8.0) > renderDistSq) {
                    continue
                }

                var color = if (isNew) newColor else oldColor

                if (smooth && !isNew) {
                    var totalWeight = 0.0

                    for (dx in -2..2) {
                        for (dz in -2..2) {
                            if (dx == 0 && dz == 0) continue

                            if (chunks[ChunkPos(chunk.x + dx, chunk.z + dz)] == true) {
                                val dist = max(abs(dx), abs(dz))

                                totalWeight += (3.0 - dist) / 2.0
                            }
                        }
                    }

                    if (totalWeight > 0.0) {
                        val ratio = (totalWeight / 12.0).coerceAtMost(1.0)

                        color = oldColor.interpolateTo(newColor, ratio)
                    }
                }

                withPositionRelativeToCamera(Vec3d(chunkX.toDouble(), drawY, chunkZ.toDouble())) {
                    drawPlane(16f, 16f, color, color.darker())
                }
            }
            commitBatch()
        }
    }
}
